package com.tarena.lbs.coupon.service.handler;

import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.model.Result;
import com.tarena.lbs.coupon.dao.repository.CouponRepository;
import com.tarena.lbs.coupon.dao.repository.StockRepository;
import com.tarena.lbs.coupon.enums.CouponEnableStatus;
import com.tarena.lbs.coupon.enums.CouponStatus;
import com.tarena.lbs.coupon.enums.CouponTypeEnum;
import com.tarena.lbs.coupon.enums.StockResultEnum;
import com.tarena.lbs.coupon.pojo.dto.CouponDto;
import com.tarena.lbs.coupon.pojo.param.CouponParam;
import com.tarena.lbs.coupon.pojo.param.UpdateCouponParam;
import com.tarena.lbs.coupon.pojo.po.CouponPO;
import com.tarena.lbs.coupon.pojo.po.CouponStockPO;
import com.tarena.lbs.coupon.utils.CouponConverter;
import com.tarena.lbs.coupon.utils.MyResult;
import com.tarena.lbs.user.api.UserApi;
import com.tarena.lbs.user.api.pojo.dto.AdminDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;

/**
 * @className: CouponTemplateHandlerImpl
 * @author: Feng-hongliang
 * @date: 2025/10/10 15:30
 * @Version: 1.0
 * @description: 优惠券模板：主要负责优惠券模板的创建、定义
 * 不包含查询
 * TODO： 烂代码越来越多，重点优化：
 * 所有对外接口应统一返回
 * 移除无用日志
 * 检查修复字段映射问题
 * ...
 */
@Slf4j
@Service("couponTemplateService")
public class CouponTemplateHandlerImpl implements CouponTemplateHandler {
    private final CouponRepository couponRepository;
    private final StockRepository stockRepository;

    public CouponTemplateHandlerImpl(
            @Qualifier("couponRepository") CouponRepository couponRepository,
            @Qualifier("stockRepository") StockRepository stockRepository) {
        this.couponRepository = couponRepository;
        this.stockRepository = stockRepository;
    }

    @DubboReference
    private UserApi userApi;

    /**
     * 支持的时间格式列表
     */
    private static final List<DateTimeFormatter> SUPPORTED_FORMATTERS = Arrays.asList(
            DateTimeFormatter.ofPattern("yyyy-MM-dd"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd H:m:s"),
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
            DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
    );

    /**
     * 创建优惠券
     * 先进行数据校验
     * 在转换为实体
     * 在根据优惠券类型判断优惠券是否符合要求
     * TODO: 对异常抛出的处理，抛出异常和事务回滚
     *
     * @param couponParam 优惠券参数
     * @return 结果
     * @throws BusinessException 业务异常
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> addCoupon(CouponParam couponParam) throws BusinessException {
        try {
            Result<Boolean> validationResult = validateCouponParam(couponParam);
            if (!validationResult.getData()) {
                log.error("参数校验失败：{}", validationResult.getMessage());
                throw new BusinessException(validationResult.getCode(), validationResult.getMessage());
            }
            Result<Boolean> result = validateByCouponType(couponParam);
            if (!result.getData()){
                log.error("优惠券类型不符合要求：{}", result.getMessage());
                throw new BusinessException(result.getCode(), result.getMessage());
            }

            CouponPO couponPO = convertToCouponPO(couponParam);

            // TODO：对商家状态的检查
//            根据管理员id去查找商家的id
            AdminDto adminDto = userApi.getAdminById(couponParam.getBusinessId());
            log.debug("管理员id的商家id：{}", adminDto.getBusinessId());
            couponPO.setBusinessId(adminDto.getBusinessId());

            // 生成优惠券
            couponRepository.generateCoupon(couponPO);

            // 新增数据成功，对优惠券库存表进行更新
            CouponStockPO couponStockPO = new CouponStockPO();
            couponStockPO.setCouponId(couponPO.getId());
            couponStockPO.setBusinessId(couponPO.getBusinessId());
//            TODO： 检查字段是否映射错误
            couponStockPO.setNum(couponParam.getMaxUsageLimit());
            couponStockPO.setCreateAt(new Date());
            couponStockPO.setUpdateAt(new Date());

            int insertStockResult = stockRepository.insert(couponStockPO);
            if (insertStockResult != 1) {
                log.error("优惠券库存插入失败，couponId={}", couponPO.getId());
                throw new BusinessException("STOCK_INSERT_FAIL", "优惠券库存插入失败");
            }

            return Result.success();
        } catch (BusinessException e) {
            log.error("业务异常：{}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            log.error("系统异常：{}", e.getMessage(), e);
            throw new BusinessException(StockResultEnum.FAILED.getCode(), StockResultEnum.FAILED.getMessage() + "：" + e.getMessage());
        }
    }

    /**
     * TODO： 返回值的思考
     * @param param 更新参数
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MyResult<String> updateCoupon(UpdateCouponParam param) {
        CouponPO original = couponRepository.findById(param.getId());
//        将新的条件查询参数转换为PO实体
        CouponPO po = convertToCouponPO(param);
        MyResult<String> dateChange = validateCouponDateChange(original, param);
        if (!dateChange.getResult()) {
            log.info("优惠券时间参数有误:{}", dateChange.getMessage());
            throw new RuntimeException(dateChange.getMessage());
        }
//        设置更新时间
        po.setUpdateAt(new Date());
        int result = couponRepository.updateCoupon(po);
        if (result != 1) {
            log.error("更新优惠券信息时出现异常，本次更新的id为{}", po.getId());
            throw new RuntimeException("更新优惠券信息时出现异常");
        }
        return null;
    }

    /**
     * 检查优惠券的时间参数 <br/>
     * TODO：目前是针对更新的，后续优化/拆分新增也可以
     * <p>
     * 当前规则: <br/>
     * 校验优惠券开始时间的修改规则
     * 如果优惠券已经开始使用，则不能将开始时间改为未来时间
     * 如果优惠券还未开始，则不能将开始时间设置为过去时间
     * </p>
     * TODO: 查询类型优化，提升复用
     *
     * @param existingCoupon 数据库中现有的优惠券信息
     * @param param          更新参数
     */
    private MyResult<String> validateCouponDateChange(CouponPO existingCoupon, UpdateCouponParam param) {
        // 解析新的开始时间
        LocalDate newStartDate = parseStringToLocalDate(param.getStartDate());
        if (newStartDate == null) {
            return MyResult.fail("开始时间格式不正确");
        }

        // 获取原开始时间
        Date originalStartDate = existingCoupon.getStartDate();
        if (originalStartDate == null) {
            return MyResult.fail("原始开始时间为空，无法执行时间变更验证");
        }
        LocalDate originalStartLocalDate = originalStartDate.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDate();


        // 获取当前日期
        LocalDate now = LocalDate.now();

        // 判断原优惠券状态
        if (originalStartLocalDate.isBefore(now) || originalStartLocalDate.isEqual(now)) {
            // 原优惠券已开始或正在开始
            if (newStartDate.isAfter(now)) {
                // 不允许将已开始的优惠券改为未开始状态
                return MyResult.fail("优惠券已开始使用，不能将开始时间改到未来");
            }
        } else {
            // 原优惠券未开始
            if (newStartDate.isBefore(now)) {
                // 不允许将未开始的优惠券改为已开始状态
                return MyResult.fail("优惠券未开始，不能将开始时间改到过去");
            }
        }
        return MyResult.success();
    }

    private CouponPO convertToCouponPO(UpdateCouponParam param) {
        CouponPO po = new CouponPO();
        // 设置基础信息
        po.setId(param.getId());
        po.setCouponName(param.getCouponName());
        po.setCouponType(param.getCouponType());
        po.setDiscountValue(param.getDiscountValue());
        po.setMaxDiscountAmount(param.getMaxDiscountAmount());
        po.setApplicable(param.getApplicable());
        po.setUsageLimit(param.getUsageLimit());
        po.setMaxUsageLimit(param.getMaxUsageLimit());
        po.setDescribes(param.getDescribes());
        po.setBusinessId(param.getBusinessId());
        po.setExclusionType(param.getExclusionType());
        po.setOrderAmount(param.getOrderAmount());
        po.setUsageNum(param.getUsageNum());
        return po;
    }

    /**
     * 将CouponParam参数转换为CouponPO对象
     *
     * @param couponParam 优惠券参数
     * @return CouponPO
     */
    private CouponPO convertToCouponPO(CouponParam couponParam) throws BusinessException {
        // 转换时间
        Map<String, LocalDate> dateMap = parseTimeString(couponParam.getStartDate(), couponParam.getEndDate());
        LocalDate start = dateMap.get("start");
        LocalDate end = dateMap.get("end");

        if (start == null || end == null) {
            log.error("时间转换失败");
            throw new RuntimeException("时间转换失败");
        }

        CouponDto couponDto = new CouponDto();
        couponDto.setCouponName(couponParam.getCouponName());
        couponDto.setCouponType(couponParam.getCouponType());
        couponDto.setDiscountValue(couponParam.getDiscountValue());
        couponDto.setMaxDiscountAmount(couponParam.getMaxDiscountAmount());
        couponDto.setApplicable(couponParam.getApplicable());

        /*
         * 数据库字段与描述存在差异，根据描述调整映射关系：
         * usage_limit` int(11) NOT NULL COMMENT '总共可被领取次数',
         * `max_usage_limit` int(11) NOT NULL COMMENT '单次消费最多使用限制',
         */
        couponDto.setUsageLimit(couponParam.getMaxUsageLimit()); // 总共可被领取次数
        couponDto.setMaxUsageLimit(couponParam.getUsageLimit()); // 单次消费最多使用限制

        couponDto.setStartDate(start);
        couponDto.setEndDate(end);
        couponDto.setDescribes(couponParam.getDescribes());
        couponDto.setBusinessId(couponParam.getBusinessId());
        couponDto.setExclusionType(couponParam.getExclusionType());
        couponDto.setOrderAmount(couponParam.getOrderAmount());
        couponDto.setUsageNum(couponParam.getUsageNum());

        // 设置默认数据
        CouponPO po = CouponConverter.toCouponPO(couponDto);
        po.setStatus(CouponStatus.ENABLED.getCode());
        po.setCreateAt(new Date());
        po.setUpdateAt(new Date());
        po.setEnableStatus(CouponEnableStatus.ENABLED.getCode());
        return po;
    }

    /**
     * 检查优惠券参数的合法性
     * <p>
     * 验证优惠券创建时所需的各项参数是否符合业务规则，包括但不限于：
     * 1. 优惠券名称长度校验
     * 2. 优惠券面额有效性校验
     * 3. 使用条件金额校验
     * 4. 有效期范围校验
     * 5. 发放数量限制校验
     *
     * @param couponParam 优惠券参数对象，包含待校验的所有参数
     * @return Result<Boolean> 校验结果，true表示参数合法，false表示参数不合法
     */
    private Result<Boolean> validateCouponParam(CouponParam couponParam) {
        String errorCode = StockResultEnum.DATA_NOT_FIND.getCode();


        // 1. 非空校验
        if (couponParam == null) {
            return new Result<>(errorCode, "优惠券参数不能为空", false);
        }

        // 2. 名称校验
        if (StringUtils.isBlank(couponParam.getCouponName()) || couponParam.getCouponName().length() > 50) {
            return new Result<>(errorCode, "优惠券名称不能为空且长度不能超过50个字符", false);
        }

        // 3. 面额校验
        if (couponParam.getDiscountValue() == null) {
            return new Result<>(errorCode, "优惠券面额不能为空", false);
        } else if (couponParam.getDiscountValue().compareTo(BigDecimal.ZERO) <= 0) {
            return new Result<>(errorCode, "优惠券面额必须大于0", false);
        }

        // 4. 时间范围校验
        Map<String, LocalDate> dateMap = parseTimeString(couponParam.getStartDate(), couponParam.getEndDate());
        if (dateMap.get("start") == null || dateMap.get("end") == null) {
            return new Result<>(StockResultEnum.DATE_ERROR.getCode(), StockResultEnum.DATE_ERROR.getMessage(), false);
        }

        LocalDate start = dateMap.get("start");
        LocalDate end = dateMap.get("end");
        if (start.isAfter(end)) {
            return new Result<>(errorCode, "开始时间不能晚于结束时间", false);
        }

        // 校验开始时间不能早于当前日期（可以是今天）
        LocalDate now = LocalDate.now();
        if (start.isBefore(now)) {
            return new Result<>(errorCode, "开始时间不能早于当前日期", false);
        }

        // 5. 数量校验
        if (couponParam.getUsageNum() == null || couponParam.getUsageNum() <= 0) {
            return new Result<>(errorCode, "发放数量必须大于0", false);
        }

        return new Result<>(true);
    }

    /**
     * 解析时间字符串
     *
     * @param startTime 开始时间的字符串
     * @param endTime   结束时间的字符串
     * @return 如果时间字符串转换成功,返回包含start和end键的Map，否则返回null
     */
    private Map<String, LocalDate> parseTimeString(String startTime, String endTime) {
        log.debug("开始进行时间解析");
        LocalDate startDate = parseStringToLocalDate(startTime);
        LocalDate endDate = parseStringToLocalDate(endTime);

        if (startDate == null || endDate == null) {
            log.error("时间转换失败");
        }

        Map<String, LocalDate> map = new HashMap<>();
        map.put("start", startDate);
        map.put("end", endDate);
        return map;
    }

    /**
     * 将时间字符串解析为LocalDate
     *
     * @param timeString 需要进行转换的时间字符串
     * @return LocalDate，如果转换成功则返回转换后的LocalDate，否则返回null
     */
    private LocalDate parseStringToLocalDate(String timeString) {
        log.debug("开始将时间字符串转换成LocalDate: {}", timeString);

        if (!isValidTimeString(timeString)) {
            return null;
        }

        // 遍历所有支持的时间格式进行解析
        for (DateTimeFormatter formatter : SUPPORTED_FORMATTERS) {
            try {
                return LocalDate.parse(timeString, formatter);
            } catch (DateTimeParseException e) {
                try {
                    LocalDateTime dateTime = LocalDateTime.parse(timeString, formatter);
                    return dateTime.toLocalDate();
                } catch (DateTimeParseException ex) {
                    log.debug("无法使用格式 {} 解析时间字符串: {}", formatter.toString(), timeString);
                }
            }
        }

        log.warn("时间字符串格式不正确: {}", timeString);
        return null;
    }

    /**
     * 检查时间字符串是否有效
     *
     * @param time 待检查的时间字符串
     * @return 如果时间字符串有效则返回true，否则返回false
     */
    private boolean isValidTimeString(String time) {
        if (StringUtils.isBlank(time)) {
            // 检查输入是否为null或空字符串
            log.warn("时间字符串为空或null: {}", time);
            return false;
        } else if (StringUtils.isNumeric(time)) {
            // 检查是否为纯数字
            log.warn("时间字符串为纯数字：{}", time);
            return false;
        } else {
            return true;
        }
    }

    /**
     * 根据优惠券类型进行特定规则的校验
     *
     * @param couponParam 优惠券参数对象
     * @return 校验结果，true表示通过，false表示失败
     */
    private Result<Boolean> validateByCouponType(CouponParam couponParam) {
        String errorCode = StockResultEnum.DATA_NOT_FIND.getCode();
        Integer couponType = couponParam.getCouponType();

        // 现金券的特殊验证逻辑
        if (CouponTypeEnum.CASH.getCode().equals(couponType)) {
            log.debug("开始现金券的数据验证");
            if (couponParam.getMaxDiscountAmount() != null &&
                    couponParam.getDiscountValue().compareTo(couponParam.getMaxDiscountAmount()) < 0) {
                return new Result<>(errorCode, "最大抵扣金额不能大于优惠券面额", false);
            }
//            每人限领（领取数量、叠加规则）*最大抵扣= 优惠券面额
            if (couponParam.getUsageNum() != null &&
                    couponParam.getDiscountValue().multiply(new BigDecimal(couponParam.getUsageNum())).compareTo(couponParam.getMaxDiscountAmount()) != 0) {
                return new Result<>(errorCode, "每人限领的合计总额应等于最大抵扣金额", false);
            }
        } else if ((CouponTypeEnum.FULL_REDUCTION.getCode().equals(couponType))) {
            log.debug("开始进行满减券的验证逻辑");
        } else if (CouponTypeEnum.FULL_DISCOUNT.getCode().equals(couponType)) {
            log.debug("开始进行满折券的验证逻辑");
        } else if (CouponTypeEnum.RANDOM.getCode().equals(couponType)) {
            log.debug("开始进行随机券的验证逻辑");
        }
        // 折扣券的特殊验证逻辑
        else if (CouponTypeEnum.DISCOUNT.getCode().equals(couponType)) {
            log.debug("开始进行折扣券的验证逻辑");
            if (couponParam.getDiscountValue() != null) {
                // 折扣值应在0-1之间（表示0%-100%）
                if (couponParam.getDiscountValue().compareTo(BigDecimal.ZERO) <= 0 ||
                        couponParam.getDiscountValue().compareTo(BigDecimal.ONE) > 0) {
                    return new Result<>(errorCode, "折扣值应在0-1之间", false);
                }
            }
        }

        return new Result<>(true);
    }
}